package rs.pnv.pcc.handler.crypto;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.keys.KeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class Encrypt {

	private static final String pccJks = "./PCC.jks";
	private static final String pccPass = "PCC";

	static {
		//staticka inicijalizacija
		Security.addProvider(new BouncyCastleProvider());
		org.apache.xml.security.Init.init();
	}


	/**
	 * Ucitava sertifikat is KS fajla
	 * alias primer
	 */
	public static Certificate readPccCertificate() {
		try {
			//kreiramo instancu KeyStore
			KeyStore ks = KeyStore.getInstance("JKS", "SUN");
			//ucitavamo podatke
			InputStream inputStream = Encrypt.class.getClassLoader().getResourceAsStream(pccJks);
			BufferedInputStream in = new BufferedInputStream(inputStream);
			
			ks.load(in, pccPass.toCharArray());

			if(ks.isKeyEntry(pccPass)) {
				Certificate cert = ks.getCertificate(pccPass);
				return cert;

			}
			else
				return null;
		} catch (KeyStoreException e) {
			e.printStackTrace();
			return null;
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
			return null;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		} catch (CertificateException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		} 
	}

	/**
	 * Generise tajni kljuc
	 */
	private static SecretKey generateDataEncryptionKey() {

		try {
			// nama u projektu treba AES sa duzinom kljuca od 128 bita
			KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
			return keyGenerator.generateKey();

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}
	}

	
	public static Document encrypt(Document doc, String certificateLocation) {
		
		try {
			//cipher za kriptovanje tajnog kljuca,
			//Koristi se Javni RSA kljuc za kriptovanje
			XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
			//inicijalizacija za kriptovanje tajnog kljuca javnim RSA kljucem

			// FIXME Pera - testirati
			URL url = new URL(certificateLocation);
			BufferedInputStream in = new BufferedInputStream(url.openStream());
			X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);
			
			keyCipher.init(XMLCipher.WRAP_MODE, certificate.getPublicKey());
			SecretKey key = generateDataEncryptionKey();
			EncryptedKey encryptedKey = keyCipher.encryptKey(doc, key);

			//cipher za kriptovanje XML-a
			XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128);

			//inicijalizacija za kriptovanje
			xmlCipher.init(XMLCipher.ENCRYPT_MODE, key);

			//u EncryptedData elementa koji se kriptuje kao KeyInfo stavljamo kriptovan tajni kljuc
			EncryptedData encryptedData = xmlCipher.getEncryptedData();
			//kreira se KeyInfo
			KeyInfo keyInfo = new KeyInfo(doc);
			keyInfo.addKeyName("Kriptovani tajni kljuc");
			//postavljamo kriptovani kljuc
			keyInfo.add(encryptedKey);
			//postavljamo KeyInfo za element koji se kriptuje
			encryptedData.setKeyInfo(keyInfo);

			//trazi se element ciji sadrzaj se kriptuje
			NodeList cvorovi = doc.getChildNodes(); // Enkriptuje se root uvek
			Element koren = (Element) cvorovi.item(0);

			xmlCipher.doFinal(doc, koren, true); //kriptuje sa sadrzaj

			return doc;

		} catch (XMLEncryptionException e) {
			e.printStackTrace();
			return null;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		
	}

}
